// =====================================================================
//
//  This file is part of the Microsoft Dynamics CRM SDK code samples.
//
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//
//  This source code is intended only as a supplement to Microsoft
//  Development Tools and/or on-line documentation.  See these other
//  materials for detailed information regarding Microsoft code samples.
//
//  THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
// =====================================================================
#region

using System;
using System.Diagnostics.CodeAnalysis;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using Microsoft.Xrm.Sdk.Client.Protocols.WSTrust;
using SL.IdentityModel.Protocols.WSTrust;

#endregion

namespace Microsoft.Xrm.Sdk.Client
{
	internal sealed partial class ServiceConfiguration<TService> : IServiceConfiguration<TService>
	{
		private static readonly object _lockObject = new object();

		private readonly CrossRealmIssuerEndpointCollection _crossRealmIssuerEndpoints =
			new CrossRealmIssuerEndpointCollection();

		private readonly ServiceEndpointMetadata _serviceEndpointMetadata;

		private readonly Uri _serviceUri;

		private ServiceConfiguration()
		{
		}

		public ServiceConfiguration(Uri serviceUri)
			: this(serviceUri, null)
		{
		}

		public ServiceConfiguration(Uri serviceUri, EventHandler<ServiceMetadataLoadedEventArgs> metadataLoaded)
		{
			MetadataLoaded += metadataLoaded;

			ClientExceptionHelper.ThrowIfNull(serviceUri, "serviceUri");

			_serviceUri = serviceUri;

			_serviceEndpointMetadata = new ServiceEndpointMetadata(ServiceUri);

			_serviceEndpointMetadata.MetadataLoaded += ServiceMetadataUtilityMetadataLoaded;
			_serviceEndpointMetadata.MetadataLoadFailure += ServiceMetadataUtilityMetadataLoadFailure;

			_serviceEndpointMetadata.Load();
		}

		#region IServiceConfiguration<TService> Members

		public ServiceEndpointMetadata ServiceEndpointMetadata
		{
			get { return _serviceEndpointMetadata; }
		}

		public event EventHandler<ServiceMetadataLoadedEventArgs> MetadataLoaded;

		/// <summary>
		/// 	Contains the STS IssuerEndpoints as determined dynamically by calls to AuthenticateCrossRealm.
		/// </summary>
		public CrossRealmIssuerEndpointCollection CrossRealmIssuerEndpoints
		{
			get { return this.ServiceEndpointMetadata.CrossRealmIssuerEndpoints; }
		}

		public Uri ServiceUri
		{
			get { return _serviceUri; }
		}

		public event EventHandler<SignOnCompletedEventArgs> SignInComplete;

		#endregion

		private void ServiceMetadataUtilityMetadataLoaded(object sender, ServiceMetadataLoadedEventArgs e)
		{
			OnMetadataLoaded(e);
		}

		private void OnMetadataLoaded(ServiceMetadataLoadedEventArgs args)
		{
			if (MetadataLoaded != null)
			{
				MetadataLoaded(this, args);
			}
		}

		private void ServiceMetadataUtilityMetadataLoadFailure(object sender, ServiceMetadataLoadedEventArgs e)
		{
			OnMetadataLoadFailure(e);
		}

		private void OnMetadataLoadFailure(ServiceMetadataLoadedEventArgs args)
		{
			if (MetadataLoadFailure != null)
			{
				MetadataLoadFailure(this, args);
			}
		}

		public event EventHandler<ServiceMetadataLoadedEventArgs> MetadataLoadFailure;

		internal void Authenticate(TokenServiceCredentialType endpointType,
								   Uri uri,
								   String keyType,
								   ClientCredentials clientCredentials,
								   RequestSecurityTokenResponse securityTokenResponse)
		{
			if (uri != null)
			{
				Authenticate(endpointType, uri.AbsoluteUri, keyType, ServiceEndpointMetadata.IssuerEndpoints,
							 clientCredentials,
							 securityTokenResponse,
							 (issueCompletedEventArgs) => FireSignonCompletedEventForExternalCaller(this, issueCompletedEventArgs)
				);
			}
		}

		internal void Authenticate(TokenServiceCredentialType endpointType,
								   String keyType,
								   ClientCredentials clientCredentials,
								   RequestSecurityTokenResponse securityTokenResponse)
		{
			if (ServiceUri != null)
			{
				Authenticate(endpointType, ServiceUri, keyType, clientCredentials,
							 securityTokenResponse);
			}
		}

		internal void Authenticate(
			TokenServiceCredentialType endpointType,
			String appliesTo,
			String keyType,
			IssuerEndpointDictionary issuerEndpoints,
			ClientCredentials clientCredentials,
			RequestSecurityTokenResponse securityTokenResponse,
			Action<IssueCompletedEventArgs> authenticationCallbackAction = null)
		{
			ClientExceptionHelper.Assert(
				ServiceEndpointMetadata.AuthenticationType == AuthenticationProviderType.Federation ||
				ServiceEndpointMetadata.AuthenticationType == AuthenticationProviderType.OnlineFederation,
				"Authenticate is not supported when not in claims mode!");

			if (authenticationCallbackAction == null)
			{
				authenticationCallbackAction = (issueCompletedEventArgs) => { FireSignonCompletedEventForExternalCaller(this, issueCompletedEventArgs); };
			}

			if (!String.IsNullOrEmpty(appliesTo))
			{
				IssuerEndpoint authEndpoint = issuerEndpoints.GetIssuerEndpoint(endpointType);

				if (authEndpoint != null)
				{
					Issue(authEndpoint,
						appliesTo,
						keyType,
						clientCredentials,
						securityTokenResponse,
						(issueCompletedEventArgs) => { authenticationCallbackAction(issueCompletedEventArgs); }
					);
				}
			}
		}

		/// <summary>
		/// 	This is the method that actually creates the trust channel factory and issues the request for the token.
		/// </summary>
		/// <param name = "issuerEndpoint">Required.  The Authentication Endpoint of the WCF service endpoint of the issuer providing authentication.</param>
		/// <param name = "appliesTo">Required.  Identifies which Relying Party registered in the trusted STS for whom  to issue the token.</param>
		/// <param name = "requestType">Optional.  Defaults to WSTrust 13 Issue request.</param>
		/// <param name = "keyType">Optional.  Only required if requesting a bearer token.</param>
		/// <param name = "clientCredentials">Optional.  If used, the client credentials wil be set based on whether the Windows token is set or the UserName token.</param>
		/// <param name = "securityTokenResponse">Optional.  When used, client credentials are bypassed and security token is used instead.</param>
		/// <returns></returns>
		[SuppressMessage("Microsoft.Usage", "CA9888:DisposeObjectsCorrectly",
			Justification = "Value is returned from method and cannot be disposed.")]
		internal void Issue(IssuerEndpoint issuerEndpoint,
							String appliesTo,
							String keyType,
							ClientCredentials clientCredentials,
							RequestSecurityTokenResponse securityTokenResponse,
							Action<IssueCompletedEventArgs> callback)
		{
			WSTrustBinding binding;

			IRequestCredentials credentials;
			if (securityTokenResponse != null)
			{
				credentials = new IssuedTokenCredentials(securityTokenResponse);
				binding = new WSTrustBindingIssuedTokenMixed();
			}
			else
			{
				credentials = new UsernameCredentials(clientCredentials.UserName.UserName, clientCredentials.UserName.Password);
				binding = new WSTrustBindingUsernameMixed();
			}

			RequestSecurityToken rst = WSTrustFactory.CreateRST(issuerEndpoint.TrustVersion, keyType);
			rst.AppliesTo = new EndpointAddress(appliesTo);

			WSTrustClient trustClient = WSTrustFactory.CreateWSTrustClient(issuerEndpoint.TrustVersion, binding, issuerEndpoint.IssuerAddress, credentials);
			trustClient.IssueCompleted += new EventHandler<IssueCompletedEventArgs>(IssueCompleted);
			trustClient.IssueAsync(rst, callback);
		}


		/// <summary>
		/// This is the method that actually creates the trust channel factory and issues the request for the token.
		/// </summary>
		/// <returns></returns>
		[SuppressMessage("Microsoft.Usage", "CA9888:DisposeObjectsCorrectly", Justification = "Value is returned from method and cannot be disposed.")]
		private void Issue(AuthenticationCredentials authenticationCredentials, Action<IssueCompletedEventArgs> callback)
		{
			ClientExceptionHelper.ThrowIfNull(authenticationCredentials, "authenticationCredentials");
			ClientExceptionHelper.ThrowIfNull(authenticationCredentials.IssuerEndpoint, "authenticationCredentials.IssuerEndpoint");
			ClientExceptionHelper.ThrowIfNull(authenticationCredentials.AppliesTo, "authenticationCredentials.AppliesTo");

			authenticationCredentials.RequestType = authenticationCredentials.IssuerEndpoint.TrustVersion == WSTrustConstants.Version.WSTrust13
										? WSTrustConstants.WSTrust13.RequestTypes.Issue
										: WSTrustConstants.WSTrust2005.RequestTypes.Issue;

			var supportingToken = (authenticationCredentials.SecurityTokenResponse != null && authenticationCredentials.SecurityTokenResponse.RequestedSecurityToken != null) ? authenticationCredentials.SecurityTokenResponse :
							 (authenticationCredentials.SupportingCredentials != null && authenticationCredentials.SupportingCredentials.SecurityTokenResponse != null && authenticationCredentials.SupportingCredentials.SecurityTokenResponse.RequestedSecurityToken != null) ? authenticationCredentials.SupportingCredentials.SecurityTokenResponse : null;

			Issue(authenticationCredentials.IssuerEndpoint,
				authenticationCredentials.AppliesTo.ToString(),
				authenticationCredentials.KeyType,
				authenticationCredentials.ClientCredentials,
				supportingToken, callback);
		}


		private void IssueCompleted(object sender, IssueCompletedEventArgs e)
		{
			var client = sender as WSTrustClient;
			client.IssueCompleted -= this.IssueCompleted;
			var action = e.UserState as Action<IssueCompletedEventArgs>;
			if (action != null)
			{
				action(e);
			}
			else
			{
				FireSignonCompletedEventForExternalCaller(this, e);
			}
		}

		private void FireSignonCompletedEventForExternalCaller(object sender, IssueCompletedEventArgs e)
		{
			if (null != SignInComplete)
			{
				SignOnCompletedEventArgs args = new SignOnCompletedEventArgs(
					e.Error,
					e.Cancelled,
					e.UserState,
					(e.Error == null && !e.Cancelled) ? e.Result : null
				);

				SignInComplete(sender, args);
			}
		}

		#region Authenticate Cross Realm

		///<summary>
		///	Authenticates based on the client credentials passed in.
		///</summary>
		///<param name = "clientCredentials">The standard ClientCredentials</param>
		///<param name = "appliesTo"></param>
		///<param name = "crossRealmSts"></param>
		///<returns>RequestSecurityTokenResponse</returns>
		public void AuthenticateCrossRealm(ClientCredentials clientCredentials, String appliesTo,
										   Uri crossRealmSts)
		{
			ClientExceptionHelper.ThrowIfNull(clientCredentials, "clientCredentials");
			ClientExceptionHelper.ThrowIfNullOrEmpty(appliesTo, "appliesTo");
			ClientExceptionHelper.ThrowIfNull(crossRealmSts, "crossRealmSts");

			AuthenticateCrossRealmCore(crossRealmSts,
				() =>
				{
					Authenticate(TokenServiceCredentialType.Username,
						appliesTo,
						String.Empty,
						CrossRealmIssuerEndpoints[crossRealmSts],
						clientCredentials,
						null);
				}
			);
		}

		/// <summary>
		/// 	Authenticates based on the security token passed in.
		/// </summary>
		/// <param name = "securityTokenResponse"></param>
		/// <param name = "appliesTo"></param>
		/// <param name = "crossRealmSts"></param>
		/// <returns>RequestSecurityTokenResponse</returns>
		public void AuthenticateCrossRealm(RequestSecurityTokenResponse securityTokenResponse, String appliesTo,
										   Uri crossRealmSts)
		{
			AuthenticateCrossRealmInternal(securityTokenResponse, appliesTo, crossRealmSts, null);
		}

		private void AuthenticateCrossRealmInternal(RequestSecurityTokenResponse securityTokenResponse, String appliesTo, Uri crossRealmSts, Action<IssueCompletedEventArgs> authenticationCallbackAction)
		{
			ClientExceptionHelper.ThrowIfNull(securityTokenResponse, "securityTokenResponse");
			ClientExceptionHelper.ThrowIfNullOrEmpty(appliesTo, "appliesTo");
			ClientExceptionHelper.ThrowIfNull(crossRealmSts, "crossRealmSts");

			AuthenticateCrossRealmCore(crossRealmSts,
				() =>
				{
					Authenticate(TokenServiceCredentialType.SymmetricToken,
						appliesTo,
						String.Empty,
						CrossRealmIssuerEndpoints[crossRealmSts],
						null,
						securityTokenResponse,
						authenticationCallbackAction);
				}
			);
		}

		private void AuthenticateCrossRealmCore(Uri crossRealmSts, Action authenticateAction)
		{
			if (crossRealmSts != null)
			{
				if (CrossRealmIssuerEndpointsAvailableForSts(crossRealmSts))
				{
					authenticateAction();
				}
				else
				{
					this.CrossRealmIssuersLoaded +=
						(s, e) =>
						{
							if (CrossRealmIssuerEndpointsAvailableForSts(crossRealmSts))
							{
								authenticateAction();
							}
							else
							{
								FireAuthenticationFailureEvent(new ArgumentException("No issuer endpoints found at the specified Cross Realm STS", "crossRealmSts"));
							}
						};
					this.LoadCrossRealmIssuers(crossRealmSts);
				}
			}
		}


		private bool CrossRealmIssuerEndpointsAvailableForSts(Uri crossRealmSts)
		{
			return CrossRealmIssuerEndpoints.ContainsKey(crossRealmSts);
		}


		private void LoadCrossRealmIssuers(Uri crossRealmSts)
		{
			ServiceEndpointMetadata crossRealmMetadata = new ServiceEndpointMetadata(crossRealmSts);
			crossRealmMetadata.MetadataLoaded += new EventHandler<ServiceMetadataLoadedEventArgs>(CrossRealmMetadataLoadedCallback);
			crossRealmMetadata.MetadataLoadFailure += new EventHandler<ServiceMetadataLoadedEventArgs>(CrossRealmMetadataLoadFailureCallback);
			crossRealmMetadata.RetrieveIssuerEndpoints(crossRealmSts);
		}

		private event EventHandler CrossRealmIssuersLoaded;

		void CrossRealmMetadataLoadedCallback(object sender, ServiceMetadataLoadedEventArgs e)
		{
			ServiceEndpointMetadata metadata = sender as ServiceEndpointMetadata;
			CrossRealmIssuerEndpoints[metadata.ServiceUri] = e.ServiceEndpointMetadata.IssuerEndpoints;
			if (CrossRealmIssuersLoaded != null)
				CrossRealmIssuersLoaded(this, null);
		}

		void CrossRealmMetadataLoadFailureCallback(object sender, ServiceMetadataLoadedEventArgs e)
		{
			FireAuthenticationFailureEvent(new ArgumentException("The specified Cross Realm STS is invalid or unreachable", "crossRealmSts"));
		}

		void FireAuthenticationFailureEvent(Exception exception)
		{
			SignOnCompletedEventArgs args = new SignOnCompletedEventArgs(
				exception,
				false,
				null,
				null);

			this.SignInComplete(this, args);
		}

		#endregion Authenticate Cross Realm

		#region Authenticate ClientCredentials

		///<summary>
		///	Authenticates based on the client credentials passed in.
		///</summary>
		///<param name = "clientCredentials">The standard ClientCredentials</param>
		///<returns>RequestSecurityTokenResponse</returns>
		public void Authenticate(ClientCredentials clientCredentials)
		{
			Authenticate(TokenServiceCredentialType.Username, String.Empty, clientCredentials, null);
		}

		/// <summary>
		/// 	Authenticates based on the client credentials passed in.
		/// </summary>
		/// <param name = "clientCredentials"></param>
		/// <param name = "keyType">Optional.  Can be set to Bearer if bearer token required</param>
		/// <returns>RequestSecurityTokenResponse</returns>
		internal void Authenticate(ClientCredentials clientCredentials, String keyType)
		{
			Authenticate(TokenServiceCredentialType.Username, keyType, clientCredentials, null);
		}




		#endregion Authenticate ClientCredentials

		#region Authenticate SecurityToken

		/// <summary>
		/// 	Authenticates based on the security token response passed in.
		/// </summary>
		/// <param name = "securityTokenResponse"></param>
		/// <returns>RequestSecurityTokenResponse</returns>
		public void Authenticate(RequestSecurityTokenResponse securityTokenResponse)
		{
			ClientExceptionHelper.ThrowIfNull(securityTokenResponse, "securityTokenResponse");

			Authenticate(securityTokenResponse, string.Empty);
		}

		/// <summary>
		/// 	Authenticates based on the security token passed in.
		/// </summary>
		/// <param name = "securityTokenResponse"></param>
		/// <param name = "keyType">Optional.  Can be set to Bearer if bearer token required</param>
		/// <returns>RequestSecurityTokenResponse</returns>
		internal void Authenticate(RequestSecurityTokenResponse securityTokenResponse, String keyType)
		{
			ClientExceptionHelper.ThrowIfNull(securityTokenResponse, "securityToken");

			Authenticate(TokenServiceCredentialType.SymmetricToken, keyType, null, securityTokenResponse);
		}

		/// <summary>
		/// 	Authenticates based on the security token passed in.
		/// </summary>
		/// <param name = "securityTokenResponse"></param>
		/// <param name = "uri"></param>
		/// <param name = "keyType">Optional.  Can be set to Bearer if bearer token required</param>
		/// <returns>RequestSecurityTokenResponse</returns>
		internal void Authenticate(RequestSecurityTokenResponse securityTokenResponse, Uri uri, String keyType)
		{
			ClientExceptionHelper.ThrowIfNull(securityTokenResponse, "securityToken");

			if (uri != null)
			{
				AuthenticationCredentials authenticationCredentials = new AuthenticationCredentials();
				authenticationCredentials.AppliesTo = uri.GetServiceRoot();
				authenticationCredentials.EndpointType = _tokenEndpointType;
				authenticationCredentials.KeyType = keyType;
				authenticationCredentials.IssuerEndpoints = IssuerEndpoints;
				authenticationCredentials.ClientCredentials = null;
				authenticationCredentials.SecurityTokenResponse = securityTokenResponse;
				AuthenticateInternal(
					authenticationCredentials,
					(issueCompletedEventArgs) => { FireSignonCompletedEventForExternalCaller(this, issueCompletedEventArgs); }
				);
			}

		}
		private TokenServiceCredentialType _tokenEndpointType = TokenServiceCredentialType.AsymmetricToken;
		public IssuerEndpointDictionary IssuerEndpoints
		{
			get { return ServiceEndpointMetadata.IssuerEndpoints; }
			//internal set;
		}
		#endregion Authenticate SecurityToken


		/// <summary>
		/// 	Authenticates based on the client credentials passed in.
		/// </summary>
		/// <param name = "clientCredentials"></param>
		/// <param name = "uri"></param>
		/// <param name = "keyType">Optional.  Can be set to Bearer if bearer token required</param>
		/// <returns>RequestSecurityTokenResponse</returns>
		internal void Authenticate(ClientCredentials clientCredentials, Uri uri, String keyType)
		{
			AuthenticationCredentials authenticationCredentials = new AuthenticationCredentials();
			authenticationCredentials.AppliesTo = uri;
			authenticationCredentials.EndpointType = GetCredentialsEndpointType(clientCredentials);
			authenticationCredentials.KeyType = keyType;
			authenticationCredentials.IssuerEndpoints = IssuerEndpoints;
			authenticationCredentials.ClientCredentials = clientCredentials;
			authenticationCredentials.SecurityTokenResponse = null;
			AuthenticateInternal(
				authenticationCredentials,
				(issueCompletedEventArgs) => { FireSignonCompletedEventForExternalCaller(this, issueCompletedEventArgs); }
			);
		}

		private TokenServiceCredentialType GetCredentialsEndpointType(ClientCredentials clientCredentials)
		{
			if (clientCredentials != null)
			{

				if (clientCredentials.UserName != null && !String.IsNullOrEmpty(clientCredentials.UserName.UserName))
				{
					return TokenServiceCredentialType.Username;
				}

				//We don't want to do anything specific so that the default credential searching can be done.
			}
			return TokenServiceCredentialType.Kerberos;
		}

		public Boolean ClaimsEnabledService
		{
			get
			{
				return (AuthenticationType == AuthenticationProviderType.Federation ||
						AuthenticationType == AuthenticationProviderType.OnlineFederation);
			}
		}

		private void AuthenticateInternal(AuthenticationCredentials authenticationCredentials, Action<IssueCompletedEventArgs> callback)
		{
			Issue(authenticationCredentials, callback);
		}

		#region Authenticate LiveId

		private LiveIDTokenManager _liveIdTokenManager = null;

		private LiveIDTokenManager LiveIdTokenManager
		{
			get
			{
				if (_liveIdTokenManager == null)
				{
					_liveIdTokenManager = new LiveIDTokenManager();

					_liveIdTokenManager.SignOnComplete +=
						new EventHandler<SignOnCompletedEventArgs>(LiveIdTokenManagerSignOnComplete);
					_liveIdTokenManager.DeviceSignOnComplete +=
						new EventHandler<SignOnCompletedEventArgs>(LiveIdTokenManagerDeviceSignOnComplete);
				}
				return _liveIdTokenManager;
			}
		}

		public void AuthenticateDevice(ClientCredentials clientCredentials)
		{
			AuthenticateLiveIdDeviceInternal(clientCredentials);
		}

		public void Authenticate(ClientCredentials clientCredentials, RequestSecurityTokenResponse deviceTokenResponse)
		{
			Authenticate(clientCredentials, deviceTokenResponse, string.Empty);
		}

		public event EventHandler<SignOnCompletedEventArgs> DeviceSignInComplete;

		public void Authenticate(ClientCredentials clientCredentials, RequestSecurityTokenResponse deviceTokenResponse,
								 String keyType)
		{
			if (deviceTokenResponse == null)
			{
				Authenticate(clientCredentials, keyType);
				return;
			}
			AuthenticateLiveIdInternal(clientCredentials, deviceTokenResponse.RequestedSecurityToken);
		}

		[SuppressMessage("Microsoft.Usage", "CA9888:DisposeObjectsCorrectly",
			Justification = "Value is returned from method and cannot be disposed.")]
		private void AuthenticateLiveIdDeviceInternal(ClientCredentials clientCredentials)
		{
			ClientExceptionHelper.Assert(
				ServiceEndpointMetadata.AuthenticationType == AuthenticationProviderType.LiveId ||
				ServiceEndpointMetadata.AuthenticationType == AuthenticationProviderType.OnlineFederation,
				"Authenticate is not supported when not in claims mode with LiveId!");

			ClientExceptionHelper.ThrowIfNull(clientCredentials, "clientCredentials");

			OnlinePolicyConfiguration liveConfiguration =
				ServiceEndpointMetadata.PolicyConfiguration as OnlinePolicyConfiguration;

			ClientExceptionHelper.ThrowIfNull(liveConfiguration, "liveConfiguration");

			IssuerEndpoint authEndpoint =
					ServiceEndpointMetadata.IssuerEndpoints.GetIssuerEndpoint(TokenServiceCredentialType.Username);

			if (authEndpoint != null)
			{
				LiveIdTokenManager.AuthenticateDevice(authEndpoint.IssuerAddress.Uri, clientCredentials);
			}
		}

		[SuppressMessage("Microsoft.Usage", "CA9888:DisposeObjectsCorrectly",
			Justification = "Value is returned from method and cannot be disposed.")]
		private void AuthenticateLiveIdInternal(ClientCredentials clientCredentials, RequestedSecurityToken deviceToken)
		{
			ClientExceptionHelper.Assert(
				ServiceEndpointMetadata.AuthenticationType == AuthenticationProviderType.LiveId ||
				ServiceEndpointMetadata.AuthenticationType == AuthenticationProviderType.OnlineFederation,
				"Authenticate is not supported when not in claims mode with LiveId!");

			ClientExceptionHelper.ThrowIfNull(clientCredentials, "clientCredentials");

			ClientExceptionHelper.ThrowIfNull(deviceToken, "deviceToken");

			OnlinePolicyConfiguration liveConfiguration =
				ServiceEndpointMetadata.PolicyConfiguration as OnlinePolicyConfiguration;

			ClientExceptionHelper.ThrowIfNull(liveConfiguration, "liveConfiguration");

			IssuerEndpoint authEndpoint =
					ServiceEndpointMetadata.IssuerEndpoints.GetIssuerEndpoint(TokenServiceCredentialType.Username);

			String appliesTo = liveConfiguration.AppliesTo;

			if (authEndpoint != null)
			{
				LiveIdTokenManager.Authenticate(authEndpoint.IssuerAddress.Uri, deviceToken, clientCredentials,
												appliesTo, liveConfiguration.Policy);
			}
		}

		private void LiveIdTokenManagerSignOnComplete(object sender, SignOnCompletedEventArgs e)
		{
			RaiseSigninEventForExternalCallers(sender, e);
		}

		private void RaiseSigninEventForExternalCallers(object sender, SignOnCompletedEventArgs e)
		{
			if (null != SignInComplete)
			{
				SignOnCompletedEventArgs args = new SignOnCompletedEventArgs(
					e.Error,
					e.Cancelled,
					e.UserState,
					(e.Error == null && !e.Cancelled) ? e.Result : null
				);
				SignInComplete(sender, args);
			}
		}

		private void LiveIdTokenManagerDeviceSignOnComplete(object sender, SignOnCompletedEventArgs e)
		{
			RaiseDeviceSigninEventForExternalCallers(sender, e);
		}

		private void RaiseDeviceSigninEventForExternalCallers(object sender, SignOnCompletedEventArgs e)
		{
			if (null != DeviceSignInComplete)
			{
				SignOnCompletedEventArgs args = new SignOnCompletedEventArgs(
					e.Error,
					e.Cancelled,
					e.UserState,
					(e.Error == null && !e.Cancelled) ? e.Result : null
				);
				DeviceSignInComplete(sender, args);
			}
		}

		#endregion Authenticate LiveId

		#region Constants

		internal const String DefaultRequestType = WSTrustConstants.WSTrust13.RequestTypes.Issue;

		#endregion Constants


		/// <summary>
		/// This defaults to the first avaialble endpoint in the ServiceEndpoints dictionary if it has not been set.
		/// </summary>
		public ServiceEndpoint CurrentServiceEndpoint
		{
			get
			{
				if (currentServiceEndpoint == null)
				{
					foreach (var endpoint in ServiceEndpoints.Values)
					{
						if (ServiceUri.Port == endpoint.Address.Uri.Port &&
						   ServiceUri.Scheme == endpoint.Address.Uri.Scheme)
						{
							currentServiceEndpoint = endpoint;
							break;
						}
					}
				}
				return currentServiceEndpoint;
			}
			set { currentServiceEndpoint = value; }
		}
		private ServiceEndpoint currentServiceEndpoint;

		/// <summary>
		/// If there is a CurrentServiceEndpoint and the Service has been configured for claims (Federation,) then this
		/// is the endpoint used by the Secure Token Service (STS) to issue the trusted token.
		/// </summary>
		public IssuerEndpoint CurrentIssuer
		{
			get
			{
				if (CurrentServiceEndpoint != null)
				{
					return ServiceMetadataUtility.GetIssuer(CurrentServiceEndpoint.Binding);
				}
				return null;
			}
			set
			{
				throw new NotImplementedException();
			}
		}


		public Microsoft.Xrm.Sdk.Client.ServiceEndpointMetadata.ServiceEndpointDictionary ServiceEndpoints { get; internal set; }
	}


	internal static class WSTrustFactory
	{
		internal static WSTrustClient CreateWSTrustClient(WSTrustConstants.Version version, WSTrustBinding binding, EndpointAddress endpointAddress, IRequestCredentials credentials)
		{
			switch (version)
			{
				case WSTrustConstants.Version.WSTrust2005:
					return new WSTrustClientForADFS(binding, endpointAddress, credentials);
				default:
					return new WSTrustClient(binding, endpointAddress, credentials);
			}
		}

		internal static RequestSecurityToken CreateRST(WSTrustConstants.Version version, string keyType)
		{
			switch (version)
			{
				case WSTrustConstants.Version.WSTrust2005:
					return new RequestSecurityTokenForADFS(keyType);
				default:
					return new RequestSecurityToken(keyType);
			}
		}
	}

	internal class WSTrustClientForADFS : WSTrustClient
	{
		internal WSTrustClientForADFS(Binding binding, EndpointAddress remoteAddress, IRequestCredentials credentials)
			: base(binding, remoteAddress, credentials)
		{
			this.IssueAction = "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue";
		}
	}

	internal class RequestSecurityTokenForADFS : RequestSecurityToken
	{
		internal RequestSecurityTokenForADFS(string keyType)
			: base(keyType)
		{
			this.RequestType = "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue";
		}
	}
}
